package com.hmall.search.service.impl;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.hmall.common.constant.ESIndex;
import com.hmall.feign.client.ItemClient;
import com.hmall.feign.pojo.Item;
import com.hmall.feign.pojo.PageDTO;

import com.hmall.search.mapper.SearchMapper;
import com.hmall.search.pojo.TbItem;
import com.hmall.search.pojo.TbItemDoc;
import com.hmall.search.service.ISearchService;
import com.hmall.search.web.request.RequestParams;


import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

@Service
@Slf4j
public class SearchService extends ServiceImpl<SearchMapper, TbItem> implements ISearchService {
    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ItemClient itemServiceClient;

    /**
     * 搜索栏自动补全功能
     *
     * @param prefix
     * @return
     */
    @Override
    public List<String> getSuggestions(String prefix) {
        try {
            // 1.准备Request
            SearchRequest request = new SearchRequest(ESIndex.INDEX_NAME);
            // 2.准备DSL
            request.source().suggest(new SuggestBuilder().addSuggestion(
                    "suggestions",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(prefix)
                            .skipDuplicates(true)
                            .size(10)
            ));
            // 3.发起请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 4.解析结果
            Suggest suggest = response.getSuggest();
            // 4.1.根据补全查询名称，获取补全结果
            CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
            // 4.2.获取options
            List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
            // 4.3.遍历
            List<String> list = new ArrayList<>(options.size());
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();
                list.add(text);
            }
            return list;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 根据搜索关键字,获取到对应的品牌,分类
     *
     * @param params 用户输入的关键字和过滤条件
     * @return 对应的品牌, 分类Map集合
     */
    @Override
    public Map<String, List<String>> filters(RequestParams params) throws IOException {


        // 1.准备request
        SearchRequest request = new SearchRequest(ESIndex.INDEX_NAME);

        // 2.准备DSL
        // 2.1query
        String key = params.getKey();

        // 去除前后多余空格
        key.trim();

        if (key == null || Objects.equals("", key)) {
            // 关键字为空,查询全部
            request.source().query(QueryBuilders.matchAllQuery());
        } else {
            // 不为空,查询all字段,提高效率
            request.source().query(QueryBuilders.matchQuery("all", key));
        }

        // 2.2 设置size
        request.source().size(0);
        // 2.3 聚合
        buildAggregation(request);

        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);

        // 4. 解析
        Map<String, List<String>> result = new HashMap<>();
        Aggregations aggregations = response.getAggregations();

        // 4.1 根据品牌名称,获取品牌结果
        List<String> brandList = getAggByName(aggregations, "brandAgg");
        result.put("brand", brandList);
        // 4.1 根据名称,获取分类结果
        List<String> category = getAggByName(aggregations, "categoryAgg");
        result.put("category", category);
        // 返回
        System.out.println("result = " + result);
        return result;
    }

    /**
     * 基本搜索功能
     * @param params
     * @return
     */
    @Override
    public PageDTO search(RequestParams params) {
        try {
            // 准备Request
            SearchRequest request = new SearchRequest(ESIndex.INDEX_NAME);
            //修改业务
            buildBasicQuery(params, request);
            //默认排序
            if (params.getSortBy().equals("default")) {
            } else if (params.getSortBy().equals("sold")) {
                //销量排序
                request.source().sort("sold", SortOrder.DESC);
            } else if (params.getSortBy().equals("price")) {
                //价格排序
                request.source().sort("price", SortOrder.DESC);
            }
            // 分页
            int page = params.getPage();
            int size = params.getSize();
            request.source().from((page - 1) * size).size(size);
            //高亮请求
            request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
            // 发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 抽取修改业务代码
     * @param params
     * @param request
     */
    private void buildBasicQuery(RequestParams params, SearchRequest request) {
        // 1.构建BooleanQuery
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 2.关键字搜索
        String key = params.getKey();
        if (key == null || "".equals(key)) {
            boolQuery.must(QueryBuilders.matchAllQuery());
        } else {
            boolQuery.must(QueryBuilders.matchQuery("all", key));
        }
        // 分类
        if (params.getCategory() != null && !Objects.equals("",params.getCategory())) {
            boolQuery.filter(QueryBuilders.termQuery("category", params.getCategory()));
        }
        // 品牌
        if (params.getBrand() != null && !Objects.equals("",params.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        // 价格
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders
                    .rangeQuery("price")
                    .gte(params.getMinPrice()*100)
                    .lte(params.getMaxPrice()*100)
            );
        }
        // 2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        // 原始查询，相关性算分的查询
                        boolQuery,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 其中的一个function score 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        // 过滤条件
                                        QueryBuilders.termQuery("isAD", true),
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        });
        // 7.放入source
        request.source().query(functionScoreQuery);
    }


    // 结果解析
    private PageDTO handleResponse(SearchResponse response) {
        // 解析响应
        SearchHits searchHits = response.getHits();
        // 获取总条数
        long total = searchHits.getTotalHits().value;
        // 文档数组
        SearchHit[] hits = searchHits.getHits();
        // 遍历
        List<TbItemDoc> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            // 获取文档
            String json = hit.getSourceAsString();
            // 反序列化
            TbItemDoc tbItemDoc = JSON.parseObject(json, TbItemDoc.class);
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (!CollectionUtils.isEmpty(highlightFields)) {
                // 根据字段名获取高亮结果
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    // 获取高亮值
                    String name = highlightField.getFragments()[0].string();
                    // 覆盖非高亮结果
                    tbItemDoc.setName(name);
                }
            }
            // 加入集合
            list.add(tbItemDoc);
        }
        // 返回
        return new PageDTO(total, list);
    }


    // +++++++++++++++++++++++++抽取方法++++++++++++++++++++++++++++++++

    public void insert() throws IOException {

        // 定义变量设置分页参数
        Integer page = 1;
        Integer size = 3000;

        // 死循环,直到数据导完
        while (true){
            // 远程调用,分页查询
            PageDTO<Item> pageDTO = itemServiceClient.list(page, size);
            List<Item> items = pageDTO.getList();

            // 准备request

            // 2.创建BulkRequest对象
            BulkRequest request = new BulkRequest();

            // 准备DSL
            for (Item item : items) {

                TbItemDoc tbItemDoc = TbItemDoc.of(item);


                request.add(new IndexRequest(ESIndex.INDEX_NAME)
                        .id(tbItemDoc.getId().toString()).source(
                                JSON.toJSONString(tbItemDoc),XContentType.JSON
                        ));
            }

            // 发送请求
            BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
            // 打印结果
            System.out.println(response.status());

            // 循环退出条件
            int count = pageDTO.getList().size();
            if (count<1){
                break;
            }

            // 改变条件
            page++;
        }


    }

    /**
     * 聚合,过滤
     *
     * @param request 设置请求信息
     */
    private void buildAggregation(SearchRequest request) {
        // 根据品牌聚合
        request.source().aggregation(AggregationBuilders
                .terms("brandAgg") // 给聚合起名字
                .field("brand")  // 参与聚合的字段
                .size(20)       // 希望返回的聚合的结果数量
        );
        // 根据分类聚合
        request.source().aggregation(AggregationBuilders
                .terms("categoryAgg") // 给聚合起名字
                .field("category")  // 参与聚合的字段
                .size(20)       // 希望返回的聚合的结果数量
        );

    }


    /**
     * 根据聚合名字Key获取对应value集合
     *
     * @param aggregations 聚合操作对象
     * @param aggName      聚合名字
     * @return
     */
    private List<String> getAggByName(Aggregations aggregations, String aggName) {
        // 4.1 更加聚合名字获取聚合结果
        Terms brandTerms = aggregations.get(aggName);

        // 4.2 获取到桶
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();

        // 4.3 遍历桶
        List<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            // 获取到key
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }


    /**
     * 删除商品MQ同步ES数据
     *
     * @param id
     */
    @Override
    public void deleteById(Long id) throws IOException {
        log.info("删除商品同步ES数据: {}", id);

        // 准备request
        DeleteRequest request = new DeleteRequest(ESIndex.INDEX_NAME, id.toString());

        // 发送请求
        client.delete(request, RequestOptions.DEFAULT);
    }

    /**
     * 新增修改商品同步ES数据
     *
     * @param id
     */
    @Override
    public void insertById(Long id) throws IOException {
        log.info("新增修改商品同步ES数据: {}", id);

        // 根据id查询
        TbItem tbItem = getById(id);

        // 转换成文档对象
        TbItemDoc tbItemDoc = new TbItemDoc(tbItem);

        // 1.准备Request对象
        IndexRequest request = new IndexRequest(ESIndex.INDEX_NAME).id(tbItemDoc.getId().toString());
        // 2.准备Json文档
        request.source(JSON.toJSONString(tbItemDoc), XContentType.JSON);
        // 3.发送请求
        client.index(request, RequestOptions.DEFAULT);


    }
}

